﻿using System;
using System.Reflection;
using Affinity.Core.Utility;

namespace Affinity.Flux.Pairings
{
    /// <summary>
    /// A pairing between an event and a method.
    /// </summary>
    public class EventMethodPairing : Pairing
    {
        /// <summary>
        /// Constructor accepting the event outlet and method inlet.
        /// </summary>
        /// <param name="inlet"></param>
        /// <param name="outlet"></param>
        /// <param name="monitor"></param>
        public EventMethodPairing(Outlet outlet, Inlet inlet, bool monitor)
            : base(outlet, inlet)
        {
            if (inlet == null) throw new ArgumentNullException("inlet");
            if (outlet == null) throw new ArgumentNullException("outlet");
            if (!(inlet.Member is MethodInfo)) throw new ArgumentException("Inlet member must be a MethodInfo.");
            if (!(outlet.Member is EventInfo)) throw new ArgumentException("Outlet member must be a EventInfo.");

            //Debug.WriteLine("[{0}] {1} Pairing", Contract, typeof(EventMethodPairing).Name);

            _monitor = monitor;
            
            var evt = (EventInfo)outlet.Member;
            _inletDelegate = inlet.GetMethodDelegate(evt.EventHandlerType);
            if (monitor) _handlerDelegate = Delegates.CreateTypedDelegateForGeneralHandler(evt.EventHandlerType, HandleOutletEvent);
            else _handlerDelegate = _inletDelegate;
                
            evt.AddEventHandler(outlet.Component, _handlerDelegate);
        }

        /// <summary>
        /// If monitoring is enabled, raised when execution flows from the inlet to the outlet.
        /// </summary>
        public event Action<EventMethodPairing, object[], TimeSpan> ExecutionPushed;

        /// <summary>
        /// Raises the ExecutionPushed event.
        /// </summary>
        private void RaiseExecutionPushed(object[] parameters, TimeSpan duration)
        {
            var executionPushed = ExecutionPushed;
            if (executionPushed != null) executionPushed(this, parameters, duration);
        }

        /// <summary>
        /// Gets whether monitoring is enabled.
        /// </summary>
        public bool Monitor
        {
            get { return _monitor; }
        }

        /// <summary>
        /// Disposes this object.
        /// </summary>
        public override void Dispose()
        {
            //Debug.WriteLine("[{0}] {1} Disposing", Contract, typeof(EventMethodPairing).Name);
            var evt = (EventInfo)Outlet.Member;
            evt.RemoveEventHandler(Outlet.Component, _handlerDelegate);
        }

        /// <summary>
        /// Handles the outlet's event when monitoring is enabled.
        /// </summary>
        /// <param name="parameters"></param>
        private void HandleOutletEvent(object[] parameters)
        {
            DateTime beginTime = DateTime.Now;
            _inletDelegate.DynamicInvoke(parameters);
            RaiseExecutionPushed(parameters, DateTime.Now - beginTime);
        }

        private readonly Delegate _handlerDelegate;
        private readonly Delegate _inletDelegate;
        private readonly bool _monitor;
    }
}
